const std = @import("std");
const PriorityQueue = std.PriorityQueue;
const AutoHashMap = std.AutoHashMap;

const CellCharType = @import("constants.zig").CellCharType;
const LevelReader = @import("level_reader.zig").LevelReader;
const PipeType = @import("pipe.zig").PipeType;
const Pipes = @import("pipes.zig").Pipes;
const Pipe = @import("pipe.zig").Pipe;
const Vector = @import("vector.zig").Vector;


pub const Level = struct {
	allocator: std.mem.Allocator = std.heap.page_allocator,
	_pipes: Pipes = undefined,
	_end_pipes: [2]*Pipe = undefined,
	_grid_size: Vector = undefined,


	pub fn init(self: *Level, filename: []const u8) !void {
		var level_reader = LevelReader(CellCharType){};
		try level_reader.init(filename);
		defer level_reader.deinit();

		self._grid_size = level_reader.get_grid_size();

		self._pipes = Pipes{};
		self._pipes.init(self._grid_size.x);

		try self.create_pipes(&level_reader);
	}


	pub fn deinit(self: *Level) void {
		self._pipes.deinit();
	}


	pub fn get_grid_size(self: *Level) Vector {
		return self._grid_size;
	}


	pub fn get_in_cell(self: *Level, x: u8, y: u8) ?*Pipe {
		return self._pipes.get_pipe(x, y);
	}


	pub fn get_start_end_pipe(self: *Level) *Pipe {
		return self._end_pipes[0];
	}


	pub fn get_end_end_pipe(self: *Level) *Pipe {
		return self._end_pipes[1];
	}


	pub fn pipes_iterator(self: *Level) AutoHashMap(u16, Pipe).Iterator {
		return self._pipes.iterator();
	}


	pub fn move_pipe(self: *Level, pipe: *Pipe, new_x: u8, new_y: u8) !*Pipe {
		return self._pipes.move_pipe(pipe, new_x, new_y);
	}


	pub fn is_completed(self: *Level) !bool {
		const start_end_pipe = self.get_start_end_pipe();
		const end_end_pipe = self.get_end_end_pipe();

		var checked_pipes = AutoHashMap(*Pipe, void).init(self.allocator);
		defer checked_pipes.deinit();
		var pipes_to_check = PriorityQueue(*Pipe, void, Pipes.pipe_cmp)
				.init(self.allocator, {});
		defer pipes_to_check.deinit();

		try pipes_to_check.add(start_end_pipe);
		while (pipes_to_check.peek()) |pipe| {
			if (Pipes.check_connected(pipe, end_end_pipe)) {
				return true;
			}
			_ = try self.step_through_line(&pipes_to_check, &checked_pipes);
		}

		return false;
	}


	pub fn step_through_line(
			self: *Level,
			queued: *PriorityQueue(*Pipe, void, Pipes.pipe_cmp),
			checked: *AutoHashMap(*Pipe, void)
	) !bool {
		if (queued.count() == 0) {
			return true;
		}

		const pipe: *Pipe = queued.remove();
		try checked.put(pipe, {});

		for (self._pipes.get_connected_pipes(pipe)) |opt_check_pipe| {
			if (opt_check_pipe == null) {
				continue;
			}

			const check_pipe = opt_check_pipe.?;
			if (!checked.contains(check_pipe)) {
				try queued.add(check_pipe);
			}
		}

		return false;
	}


	fn calc_key(self: *Level, x: u8, y: u8) u16 {
		return x + @as(u16, y) * self._grid_size.x;
	}


	fn create_pipes(self: *Level, level_reader: *const LevelReader(CellCharType)
			) !void {
		const horiz_pipes = level_reader.get_positions('═');
		const vert_pipes = level_reader.get_positions('║');
		const corner_1_pipes = level_reader.get_positions('╔');
		const corner_2_pipes = level_reader.get_positions('╗');
		const corner_3_pipes = level_reader.get_positions('╝');
		const corner_4_pipes = level_reader.get_positions('╚');
		const end_1_pipes = level_reader.get_positions('╞');
		const end_2_pipes = level_reader.get_positions('╥');
		const end_3_pipes = level_reader.get_positions('╡');
		const end_4_pipes = level_reader.get_positions('╨');

		var end_pipe_pos: u2 = 0;

		if (horiz_pipes) |h_pipes| {
			for (h_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.HORIZONTAL);
			}
		}

		if (vert_pipes) |v_pipes| {
			for (v_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.VERTICAL);
			}
		}

		if (corner_1_pipes) |c1_pipes| {
			for (c1_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.CORNER_1);
			}
		}

		if (corner_2_pipes) |c2_pipes| {
			for (c2_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.CORNER_2);
			}
		}

		if (corner_3_pipes) |c3_pipes| {
			for (c3_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.CORNER_3);
			}
		}

		if (corner_4_pipes) |c4_pipes| {
			for (c4_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.CORNER_4);
			}
		}

		if (end_1_pipes) |e1_pipes| {
			for (e1_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.END_1);
				self._end_pipes[end_pipe_pos] = self._pipes.get_pipe(
						position.x, position.y).?;
				end_pipe_pos +|= 1;
			}
		}

		if (end_2_pipes) |e2_pipes| {
			for (e2_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.END_2);
				self._end_pipes[end_pipe_pos] = self._pipes.get_pipe(
						position.x, position.y).?;
				end_pipe_pos +|= 1;
			}
		}

		if (end_3_pipes) |e3_pipes| {
			for (e3_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.END_3);
				self._end_pipes[end_pipe_pos] = self._pipes.get_pipe(
						position.x, position.y).?;
				end_pipe_pos +|= 1;
			}
		}

		if (end_4_pipes) |e4_pipes| {
			for (e4_pipes.items) |position| {
				try self._pipes.add_pipe(
						position.x, position.y, PipeType.END_4);
				self._end_pipes[end_pipe_pos] = self._pipes.get_pipe(
						position.x, position.y).?;
				end_pipe_pos +|= 1;
			}
		}
	}
};
